/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


// r_backEnd.cpp -- main back-end interface


#include "r_local.h"


backEndState_t			backEnd;


/*
 ==============================================================================

 MATERIAL SETUP

 ==============================================================================
*/


/*
 ==================
 RB_DeformExpand
 ==================
*/
static void RB_DeformExpand (void){

	glVertex_t	*vertices = backEnd.vertices;
	float		expand;
	int			i;

	tr.pc.deformExpands++;
	tr.pc.deformIndices += backEnd.numIndices;
	tr.pc.deformVertices += backEnd.numVertices;

	expand = backEnd.material->expressionRegisters[backEnd.material->deformRegisters[0]];

	for (i = 0; i < backEnd.numVertices; i++)
		vertices[i].xyz += vertices[i].normal * expand;
}

/*
 ==================
 RB_DeformMove
 ==================
*/
static void RB_DeformMove (void){

	glVertex_t	*vertices = backEnd.vertices;
	odVec3		move;
	int			i;

	tr.pc.deformMoves++;
	tr.pc.deformIndices += backEnd.numIndices;
	tr.pc.deformVertices += backEnd.numVertices;

	move[0] = backEnd.material->expressionRegisters[backEnd.material->deformRegisters[0]];
	move[1] = backEnd.material->expressionRegisters[backEnd.material->deformRegisters[1]];
	move[2] = backEnd.material->expressionRegisters[backEnd.material->deformRegisters[2]];

	for (i = 0; i < backEnd.numVertices; i++)
		vertices[i].xyz += move;
}

/*
 ==================
 RB_DeformSprite
 ==================
*/
static void RB_DeformSprite (void){

	glIndex_t	*indices = backEnd.indices;
	glVertex_t	*vertices = backEnd.vertices;
	odMat3		axis;
	odVec3		center;
	float		radius;
	int			i;

	if (backEnd.numIndices != (backEnd.numVertices >> 2) * 6){
		Com_Printf(S_COLOR_YELLOW "Material '%s' has 'deform sprite' with an odd index count\n", backEnd.material->name);
		return;
	}
	if (backEnd.numVertices & 3){
		Com_Printf(S_COLOR_YELLOW "Material '%s' has 'deform sprite' with an odd vertex count\n", backEnd.material->name);
		return;
	}

	tr.pc.deformSprites++;
	tr.pc.deformIndices += backEnd.numIndices;
	tr.pc.deformVertices += backEnd.numVertices;

	for (i = 0; i < backEnd.numVertices; i += 4){
		// Compute center
		center = (vertices[0].xyz + vertices[1].xyz + vertices[2].xyz + vertices[3].xyz) * 0.25f;

		// Compute radius
		radius = (vertices->xyz - center).LengthFast() * M_SQRT_HALF;

		// Compute axes
		axis[0] = -backEnd.localParms.viewAxis[0];
		axis[1] = backEnd.localParms.viewAxis[1] * radius;
		axis[2] = backEnd.localParms.viewAxis[2] * radius;

		// Modify indices
		indices[0] = i;
		indices[1] = i + 1;
		indices[2] = i + 3;
		indices[3] = i + 3;
		indices[4] = i + 1;
		indices[5] = i + 2;

		indices += 6;

		// Modify vertices, leaving colors alone
		vertices[0].xyz = center + axis[1] + axis[2];
		vertices[0].normal = axis[0];
		vertices[0].st.Set(0.0f, 0.0f);

		vertices[1].xyz = center - axis[1] + axis[2];
		vertices[1].normal = axis[0];
		vertices[1].st.Set(1.0f, 0.0f);

		vertices[2].xyz = center - axis[1] - axis[2];
		vertices[2].normal = axis[0];
		vertices[2].st.Set(1.0f, 1.0f);

		vertices[3].xyz = center + axis[1] - axis[2];
		vertices[3].normal = axis[0];
		vertices[3].st.Set(0.0f, 1.0f);

		vertices += 4;
	}
}

/*
 ==================
 RB_DeformTube
 ==================
*/
static void RB_DeformTube (void){

	glIndex_t	*indices = backEnd.indices;
	glVertex_t	*vertices = backEnd.vertices;
	odMat3		axis;
	odVec3		center, start, end;
	odVec3		dir, edges[3];
	float		length[3];
	int			shortAxis, longAxis;
	int			i;

	if (backEnd.numIndices != (backEnd.numVertices >> 2) * 6){
		Com_Printf(S_COLOR_YELLOW "Material '%s' has 'deform tube' with an odd index count\n", backEnd.material->name);
		return;
	}
	if (backEnd.numVertices & 3){
		Com_Printf(S_COLOR_YELLOW "Material '%s' has 'deform tube' with an odd vertex count\n", backEnd.material->name);
		return;
	}

	tr.pc.deformTubes++;
	tr.pc.deformIndices += backEnd.numIndices;
	tr.pc.deformVertices += backEnd.numVertices;

	for (i = 0; i < backEnd.numVertices; i += 4){
		// Compute center
		center = (vertices[0].xyz + vertices[1].xyz + vertices[2].xyz + vertices[3].xyz) * 0.25f;

		// Compute edges
		edges[0] = vertices[1].xyz - vertices[0].xyz;
		edges[1] = vertices[2].xyz - vertices[0].xyz;
		edges[2] = vertices[2].xyz - vertices[1].xyz;

		// Compute length for each edge
		length[0] = edges[0].LengthFast();
		length[1] = edges[1].LengthFast();
		length[2] = edges[2].LengthFast();

		// Find the shortest and longest axes
		if (length[2] > length[1] && length[2] > length[0]){
			if (length[1] > length[0]){
				shortAxis = 0;
				longAxis = 1;
			}
			else {
				shortAxis = 1;
				longAxis = 0;
			}
		}
		else if (length[1] > length[2] && length[1] > length[0]){
			if (length[2] > length[0]){
				shortAxis = 0;
				longAxis = 2;
			}
			else {
				shortAxis = 2;
				longAxis = 0;
			}
		}
		else if (length[0] > length[1] && length[0] > length[2]){
			if (length[2] > length[1]){
				shortAxis = 1;
				longAxis = 2;
			}
			else {
				shortAxis = 2;
				longAxis = 1;
			}
		}
		else {
			shortAxis = 0;
			longAxis = 0;
		}

		// Compute direction
		dir = edges[longAxis];
		dir.NormalizeFast();

		// Compute start and end positions
		start = center + dir * (length[longAxis] * 0.5f);
		end = center - dir * (length[longAxis] * 0.5f);

		// Compute orientation vectors
		axis[0] = backEnd.localParms.viewOrigin - start;
		axis[1] = end - start;

		axis[2].Cross(axis[0], axis[1]);
		axis[2].NormalizeFast();

		// Compute normal
		axis[0].Cross(axis[1], axis[2]);
		axis[0].NormalizeFast();

		// Scale by half-length
		axis[2] *= length[shortAxis] * 0.5f;

		// Modify indices
		indices[0] = i;
		indices[1] = i + 1;
		indices[2] = i + 3;
		indices[3] = i + 3;
		indices[4] = i + 1;
		indices[5] = i + 2;

		indices += 6;

		// Modify vertices, leaving colors alone
		vertices[0].xyz = start + axis[2];
		vertices[0].normal = axis[0];
		vertices[0].st.Set(0.0f, 0.0f);

		vertices[1].xyz = end + axis[2];
		vertices[1].normal = axis[0];
		vertices[1].st.Set(1.0f, 0.0f);

		vertices[2].xyz = end - axis[2];
		vertices[2].normal = axis[0];
		vertices[2].st.Set(1.0f, 1.0f);

		vertices[3].xyz = start - axis[2];
		vertices[3].normal = axis[0];
		vertices[3].st.Set(0.0f, 1.0f);

		vertices += 4;
	}
}

/*
 ==================
 RB_DeformFlare

 FIXME: texture coords and colors are wrong
 ==================
*/
static void RB_DeformFlare (void){

	glIndex_t	*indices = backEnd.indices;
	glVertex_t	*vertices = backEnd.vertices;
	int			edgeIndices2[4][2];
	int			edgeIndices3[4][3];
	odVec3		dir, edges[2];
	float		dist, size;
	byte		vertexColor[4];
	int			i, index = 4;

	if (backEnd.numIndices != 6){
		Com_Printf(S_COLOR_YELLOW "Material '%s' has 'deform flare' but is not a single quad\n", backEnd.material->name);
		return;
	}
	if (backEnd.numVertices != 4){
		Com_Printf(S_COLOR_YELLOW "Material '%s' has 'deform flare' but is not a single quad\n", backEnd.material->name);
		return;
	}

	dist = vertices->normal.Dot(backEnd.localParms.viewOrigin) - vertices->normal.Dot(vertices->xyz);
	if (dist <= 0.0f)
		return;

	size = backEnd.material->expressionRegisters[backEnd.material->deformRegisters[0]];
	if (size <= 0.0f)
		return;

	tr.pc.deformFlares++;
	tr.pc.deformIndices += 54;
	tr.pc.deformVertices += 16;

	// Compute vertex color
	dist = Min(dist, size) / size;

	*(dword *)vertexColor = PackColor4(dist, dist, dist, dist);

	// Build edge index tables
	for (i = 5; i >= 3; i--){
		if (indices[i] != indices[0] && indices[i] != indices[1] && indices[i] != indices[2])
			break;
	}

	edgeIndices2[0][0] = indices[1];
	edgeIndices2[0][1] = indices[i];
	edgeIndices2[1][0] = indices[i];
	edgeIndices2[1][1] = indices[2];
	edgeIndices2[2][0] = indices[2];
	edgeIndices2[2][1] = indices[0];
	edgeIndices2[3][0] = indices[0];
	edgeIndices2[3][1] = indices[1];

	edgeIndices3[0][0] = indices[i];
	edgeIndices3[0][1] = 5;
	edgeIndices3[0][2] = 6;
	edgeIndices3[1][0] = indices[2];
	edgeIndices3[1][1] = 7;
	edgeIndices3[1][2] = 8;
	edgeIndices3[2][0] = indices[0];
	edgeIndices3[2][1] = 9;
	edgeIndices3[2][2] = 10;
	edgeIndices3[3][0] = indices[1];
	edgeIndices3[3][1] = 11;
	edgeIndices3[3][2] = 4;

	indices += 6;

	// Modify vertices, leaving positions alone
	vertices[0].st.Set(0.5f, 0.5f);
	*(dword *)vertices[0].color = *(dword *)vertexColor;

	vertices[1].st.Set(0.5f, 0.5f);
	*(dword *)vertices[1].color = *(dword *)vertexColor;

	vertices[2].st.Set(0.5f, 0.5f);
	*(dword *)vertices[2].color = *(dword *)vertexColor;

	vertices[3].st.Set(0.5f, 0.5f);
	*(dword *)vertices[3].color = *(dword *)vertexColor;

	// Generate the borders
	for (i = 0; i < 4; i++){
		// Compute edges
		edges[0] = backEnd.localParms.viewOrigin - vertices[edgeIndices2[i][0]].xyz;
		edges[1] = vertices[edgeIndices2[i][1]].xyz - vertices[edgeIndices2[i][0]].xyz;

		// Compute direction
		dir.Cross(edges[0], edges[1]);
		dir.NormalizeFast();

		// Scale by size
		dir *= size;

		// Generate new indices
		indices[0] = edgeIndices2[i][0];
		indices[1] = index;
		indices[2] = index + 1;
		indices[3] = edgeIndices2[i][0];
		indices[4] = index + 1;
		indices[5] = edgeIndices2[i][1];

		indices += 6;

		// Generate new vertices
		vertices[index+0].xyz = vertices[edgeIndices2[i][0]].xyz + dir;
		vertices[index+0].st.Set(0.0f, 0.5f);
		*(dword *)vertices[index+0].color = *(dword *)vertexColor;

		vertices[index+1].xyz = vertices[edgeIndices2[i][1]].xyz + dir;
		vertices[index+1].st.Set(0.0f, 0.5f);
		*(dword *)vertices[index+1].color = *(dword *)vertexColor;

		index += 2;
	}

	for (i = 0; i < 4; i++){
		// Compute edges
		edges[0] = vertices[edgeIndices3[i][1]].xyz - vertices[edgeIndices3[i][0]].xyz;
		edges[1] = vertices[edgeIndices3[i][2]].xyz - vertices[edgeIndices3[i][0]].xyz;

		// Compute direction
		dir = edges[0] + edges[1];
		dir.NormalizeFast();

		// Scale by size
		dir *= size;

		// Generate new indices
		indices[0] = edgeIndices3[i][0];
		indices[1] = edgeIndices3[i][1];
		indices[2] = index;
		indices[3] = edgeIndices3[i][0];
		indices[4] = index;
		indices[5] = edgeIndices3[i][2];

		indices += 6;

		// Generate new vertices
		vertices[index].xyz = vertices[edgeIndices3[i][0]].xyz + dir;
		vertices[index].st.Set(0.0f, 0.5f);
		*(dword *)vertices[index].color = *(dword *)vertexColor;

		index += 1;
	}

	backEnd.numIndices = 54;
	backEnd.numVertices = 16;
}

/*
 ==================
 RB_Deform
 ==================
*/
void RB_Deform (void){

	if (r_skipDeforms->integerValue)
		return;

	switch (backEnd.material->deform){
	case DFRM_NONE:

		break;
	case DFRM_EXPAND:
		RB_DeformExpand();
		break;
	case DFRM_MOVE:
		RB_DeformMove();
		break;
	case DFRM_SPRITE:
		RB_DeformSprite();
		break;
	case DFRM_TUBE:
		RB_DeformTube();
		break;
	case DFRM_FLARE:
		RB_DeformFlare();
		break;
	default:
		Com_Error(false, "RB_Deform: unknown deform in material '%s'", backEnd.material->name);
	}
}

/*
 ==================
 RB_MultiplyTextureMatrix
 ==================
*/
void RB_MultiplyTextureMatrix (material_t *material, textureStage_t *textureStage, odMat4 &matrix){

	float	s, t, angle;
	int		i;

	matrix.Identity();

	for (i = 0; i < textureStage->numTexMods; i++){
		switch (textureStage->texMods[i]){
		case TM_TRANSLATE:
			s = material->expressionRegisters[textureStage->texModsRegisters[i][0]];
			t = material->expressionRegisters[textureStage->texModsRegisters[i][1]];

			matrix.Translate(s, t, 0.0f);

			break;
		case TM_SCROLL:
			s = material->expressionRegisters[textureStage->texModsRegisters[i][0]];
			t = material->expressionRegisters[textureStage->texModsRegisters[i][1]];

			matrix.Translate(s, t, 0.0f);

			break;
		case TM_SCALE:
			s = material->expressionRegisters[textureStage->texModsRegisters[i][0]];
			t = material->expressionRegisters[textureStage->texModsRegisters[i][1]];

			matrix.Scale(s, t, 1.0f);

			break;
		case TM_CENTERSCALE:
			s = material->expressionRegisters[textureStage->texModsRegisters[i][0]];
			t = material->expressionRegisters[textureStage->texModsRegisters[i][1]];

			matrix.Translate(0.5f, 0.5f, 0.0f);
			matrix.Scale(s, t, 1.0f);
			matrix.Translate(-0.5f, -0.5f, 0.0f);

			break;
		case TM_SHEAR:
			s = material->expressionRegisters[textureStage->texModsRegisters[i][0]];
			t = material->expressionRegisters[textureStage->texModsRegisters[i][1]];

			matrix.Translate(0.5f, 0.5f, 0.0f);
			matrix.Shear(s, t, 1.0f);
			matrix.Translate(-0.5f, -0.5f, 0.0f);

			break;
		case TM_ROTATE:
			angle = material->expressionRegisters[textureStage->texModsRegisters[i][0]];

			matrix.Translate(0.5f, 0.5f, 0.0f);
			matrix.Rotate(angle, 0.0f, 0.0f, 1.0f);
			matrix.Translate(-0.5f, -0.5f, 0.0f);

			break;
		default:
			Com_Error(false, "RB_MultiplyTextureMatrix: unknown texMod in material '%s'", material->name);
		}
	}
}

/*
 ==================
 RB_SetupTextureStage
 ==================
*/
void RB_SetupTextureStage (textureStage_t *textureStage){

	odMat4	textureMatrix;
	bool	identity;

	// Set up the coordinates
	switch (textureStage->texGen){
	case TG_EXPLICIT:
		qglEnableClientState(GL_TEXTURE_COORD_ARRAY);
		qglTexCoordPointer(2, GL_FLOAT, sizeof(glVertex_t), POINTER_ST(backEnd.vertexPointer));

		identity = true;

		break;
	case TG_REFLECT:
		qglEnableClientState(GL_NORMAL_ARRAY);
		qglNormalPointer(GL_FLOAT, sizeof(glVertex_t), POINTER_NORMAL(backEnd.vertexPointer));

		GL_Enable(GL_TEXTURE_GEN_S);
		GL_Enable(GL_TEXTURE_GEN_T);
		GL_Enable(GL_TEXTURE_GEN_R);

		GL_TexGen(GL_S, GL_REFLECTION_MAP);
		GL_TexGen(GL_T, GL_REFLECTION_MAP);
		GL_TexGen(GL_R, GL_REFLECTION_MAP);

		textureMatrix = backEnd.viewParms.modelviewMatrix.Transpose();

		identity = false;

		break;
	case TG_NORMAL:
		qglEnableClientState(GL_NORMAL_ARRAY);
		qglNormalPointer(GL_FLOAT, sizeof(glVertex_t), POINTER_NORMAL(backEnd.vertexPointer));

		GL_Enable(GL_TEXTURE_GEN_S);
		GL_Enable(GL_TEXTURE_GEN_T);
		GL_Enable(GL_TEXTURE_GEN_R);

		GL_TexGen(GL_S, GL_NORMAL_MAP);
		GL_TexGen(GL_T, GL_NORMAL_MAP);
		GL_TexGen(GL_R, GL_NORMAL_MAP);

		textureMatrix = backEnd.localParms.viewMatrix.Transpose();

		identity = false;

		break;
	case TG_SKYBOX:
		GL_Enable(GL_TEXTURE_GEN_S);
		GL_Enable(GL_TEXTURE_GEN_T);
		GL_Enable(GL_TEXTURE_GEN_R);

		GL_TexGen(GL_S, GL_OBJECT_LINEAR);
		GL_TexGen(GL_T, GL_OBJECT_LINEAR);
		GL_TexGen(GL_R, GL_OBJECT_LINEAR);

		qglTexGenfv(GL_S, GL_OBJECT_PLANE, backEnd.viewParms.skyBoxMatrix[0].ToFloatPtr());
		qglTexGenfv(GL_T, GL_OBJECT_PLANE, backEnd.viewParms.skyBoxMatrix[1].ToFloatPtr());
		qglTexGenfv(GL_R, GL_OBJECT_PLANE, backEnd.viewParms.skyBoxMatrix[2].ToFloatPtr());

		identity = true;

		break;
	case TG_PORTALSKY:
		GL_Enable(GL_TEXTURE_GEN_S);
		GL_Enable(GL_TEXTURE_GEN_T);
		GL_Enable(GL_TEXTURE_GEN_Q);

		GL_TexGen(GL_S, GL_OBJECT_LINEAR);
		GL_TexGen(GL_T, GL_OBJECT_LINEAR);
		GL_TexGen(GL_Q, GL_OBJECT_LINEAR);

		qglTexGenfv(GL_S, GL_OBJECT_PLANE, backEnd.viewParms.portalSkyMatrix[0].ToFloatPtr());
		qglTexGenfv(GL_T, GL_OBJECT_PLANE, backEnd.viewParms.portalSkyMatrix[1].ToFloatPtr());
		qglTexGenfv(GL_Q, GL_OBJECT_PLANE, backEnd.viewParms.portalSkyMatrix[3].ToFloatPtr());

		identity = true;

		break;
	case TG_SCREEN:
		GL_Enable(GL_TEXTURE_GEN_S);
		GL_Enable(GL_TEXTURE_GEN_T);
		GL_Enable(GL_TEXTURE_GEN_Q);

		GL_TexGen(GL_S, GL_OBJECT_LINEAR);
		GL_TexGen(GL_T, GL_OBJECT_LINEAR);
		GL_TexGen(GL_Q, GL_OBJECT_LINEAR);

		qglTexGenfv(GL_S, GL_OBJECT_PLANE, backEnd.viewParms.screenMatrix[0].ToFloatPtr());
		qglTexGenfv(GL_T, GL_OBJECT_PLANE, backEnd.viewParms.screenMatrix[1].ToFloatPtr());
		qglTexGenfv(GL_Q, GL_OBJECT_PLANE, backEnd.viewParms.screenMatrix[3].ToFloatPtr());

		identity = true;

		break;
	default:
		Com_Error(false, "RB_SetupTextureStage: unknown texGen in material '%s'", backEnd.material->name);
	}

	// Set up the matrix
	if (identity){
		if (!textureStage->numTexMods)
			GL_LoadIdentity(GL_TEXTURE);
		else {
			RB_MultiplyTextureMatrix(backEnd.material, textureStage, textureMatrix);

			GL_LoadMatrix(GL_TEXTURE, textureMatrix);
		}
	}
	else
		GL_LoadMatrix(GL_TEXTURE, textureMatrix);

	// Set up the texture
	GL_EnableTexture(textureStage->texture->target);
	GL_BindTexture(textureStage->texture);

	if (textureStage->texture == tr.currentRenderTexture){
		if (backEnd.material->sort != SORT_POST_PROCESS)
			RB_CopyFrameBuffer(tr.currentRenderTexture);

		return;
	}

	if (textureStage->video){
		R_UpdateVideo(textureStage->video, SEC2MS(backEnd.time));
		return;
	}
}

/*
 ==================
 RB_CleanupTextureStage
 ==================
*/
void RB_CleanupTextureStage (textureStage_t *textureStage){

	if (textureStage->texGen == TG_EXPLICIT){
		qglDisableClientState(GL_TEXTURE_COORD_ARRAY);
		return;
	}

	if (textureStage->texGen == TG_REFLECT || textureStage->texGen == TG_NORMAL)
		qglDisableClientState(GL_NORMAL_ARRAY);

	GL_Disable(GL_TEXTURE_GEN_Q);
	GL_Disable(GL_TEXTURE_GEN_R);
	GL_Disable(GL_TEXTURE_GEN_T);
	GL_Disable(GL_TEXTURE_GEN_S);
}

/*
 ==================
 RB_SetupColorStage
 ==================
*/
void RB_SetupColorStage (colorStage_t *colorStage){

	odVec4	color;
	bool	identity;

	// Set up the color
	color[0] = backEnd.material->expressionRegisters[colorStage->registers[0]];
	color[1] = backEnd.material->expressionRegisters[colorStage->registers[1]];
	color[2] = backEnd.material->expressionRegisters[colorStage->registers[2]];
	color[3] = backEnd.material->expressionRegisters[colorStage->registers[3]];

	if (color[0] >= 1.0f && color[1] >= 1.0f && color[2] >= 1.0f && color[3] >= 1.0f)
		identity = true;
	else
		identity = false;

	// Set up the vertex color if needed
	switch (colorStage->vertexColor){
	case VC_IGNORE:
		if (identity){
			qglColor3f(1.0f, 1.0f, 1.0f);

			GL_TexEnv(GL_REPLACE);
			break;
		}

		qglColor4fv(color.ToFloatPtr());

		GL_TexEnv(GL_MODULATE);

		break;
	case VC_MODULATE:
		qglEnableClientState(GL_COLOR_ARRAY);
		qglColorPointer(4, GL_UNSIGNED_BYTE, sizeof(glVertex_t), POINTER_COLOR(backEnd.vertexPointer));

		GL_TexEnv(GL_MODULATE);

		if (identity)
			break;

		GL_SelectTexture(1);
		GL_EnableTexture(GL_TEXTURE_2D);
		GL_BindTexture(tr.whiteTexture);

		GL_TexEnv(GL_COMBINE);

		qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_CONSTANT);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
		qglTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE, 1);

		qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PREVIOUS);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_CONSTANT);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
		qglTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1);

		qglTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color.ToFloatPtr());

		GL_SelectTexture(0);

		break;
	case VC_INVERSE_MODULATE:
		qglEnableClientState(GL_COLOR_ARRAY);
		qglColorPointer(4, GL_UNSIGNED_BYTE, sizeof(glVertex_t), POINTER_COLOR(backEnd.vertexPointer));

		GL_TexEnv(GL_COMBINE);

		qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PRIMARY_COLOR);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_ONE_MINUS_SRC_COLOR);
		qglTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE, 1);

		qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_PRIMARY_COLOR);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		qglTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1);

		if (identity)
			break;

		GL_SelectTexture(1);
		GL_EnableTexture(GL_TEXTURE_2D);
		GL_BindTexture(tr.whiteTexture);

		GL_TexEnv(GL_COMBINE);

		qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_CONSTANT);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
		qglTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE, 1);

		qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PREVIOUS);
		qglTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_CONSTANT);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
		qglTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
		qglTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1);

		qglTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color.ToFloatPtr());

		GL_SelectTexture(0);

		break;
	default:
		Com_Error(false, "RB_SetupColorStage: unknown vertexColor in material '%s'", backEnd.material->name);
	}
}

/*
 ==================
 RB_CleanupColorStage
 ==================
*/
void RB_CleanupColorStage (colorStage_t *colorStage){

	if (colorStage->vertexColor == VC_IGNORE)
		return;

	qglDisableClientState(GL_COLOR_ARRAY);

	GL_SelectTexture(1);
	GL_DisableTexture();
	GL_SelectTexture(0);
}

/*
 ==================
 RB_SetupShaderStage
 ==================
*/
void RB_SetupShaderStage (shaderStage_t *shaderStage){

	shaderParm_t	*shaderParm;
	shaderMap_t		*shaderMap;
	uniform_t		*uniform;
	odVec4			parm;
	int				i;

	// Bind the program
	qglUseProgram(shaderStage->program->programId);

	if (backEnd.overlay){
		// Set up the arrays
		qglEnableClientState(GL_TEXTURE_COORD_ARRAY);
		qglTexCoordPointer(2, GL_FLOAT, sizeof(glVertex_t), POINTER_ST(backEnd.vertexPointer));

		qglEnableClientState(GL_COLOR_ARRAY);
		qglColorPointer(4, GL_UNSIGNED_BYTE, sizeof(glVertex_t), POINTER_COLOR(backEnd.vertexPointer));

		// Set up the internal uniforms
		for (i = 0, uniform = shaderStage->program->uniforms; i < shaderStage->program->numUniforms; i++, uniform++){
			if (uniform->type == UT_CUSTOM)
				continue;

			switch (uniform->type){
			case UT_XY_SCALE:
				qglUniform2fv(uniform->location, 1, backEnd.xyScale.ToFloatPtr());
				break;
			case UT_POT_ADJUST:
				qglUniform2fv(uniform->location, 1, backEnd.potAdjust.ToFloatPtr());
				break;
			case UT_LIGHT_SCALE:
				qglUniform1f(uniform->location, (r_lightScale->floatValue > 1.0f) ? r_lightScale->floatValue : 1.0f);
				break;
			default:
				Com_Error(false, "RB_SetupShaderStage: unknown shader uniform type in material '%s'", backEnd.material->name);
			}
		}
	}
	else {
		// Set up the arrays
		qglEnableClientState(GL_NORMAL_ARRAY);
		qglNormalPointer(GL_FLOAT, sizeof(glVertex_t), POINTER_NORMAL(backEnd.vertexPointer));

		qglEnableClientState(GL_TEXTURE_COORD_ARRAY);
		qglTexCoordPointer(2, GL_FLOAT, sizeof(glVertex_t), POINTER_ST(backEnd.vertexPointer));

		qglEnableClientState(GL_COLOR_ARRAY);
		qglColorPointer(4, GL_UNSIGNED_BYTE, sizeof(glVertex_t), POINTER_COLOR(backEnd.vertexPointer));

		qglEnableVertexAttribArray(10);
		qglVertexAttribPointer(10, 3, GL_FLOAT, false, sizeof(glVertex_t), POINTER_TANGENT0(backEnd.vertexPointer));

		qglEnableVertexAttribArray(11);
		qglVertexAttribPointer(11, 3, GL_FLOAT, false, sizeof(glVertex_t), POINTER_TANGENT1(backEnd.vertexPointer));

		// Set up the internal uniforms
		for (i = 0, uniform = shaderStage->program->uniforms; i < shaderStage->program->numUniforms; i++, uniform++){
			if (uniform->type == UT_CUSTOM)
				continue;

			switch (uniform->type){
			case UT_VIEW_ORIGIN:
				qglUniform3fv(uniform->location, 1, backEnd.localParms.viewOrigin.ToFloatPtr());
				break;
			case UT_VIEW_AXIS:
				qglUniformMatrix3fv(uniform->location, 1, true, backEnd.localParms.viewAxis.ToFloatPtr());
				break;
			case UT_ENTITY_ORIGIN:
				qglUniform3fv(uniform->location, 1, backEnd.entity->e.origin.ToFloatPtr());
				break;
			case UT_ENTITY_AXIS:
				qglUniformMatrix3fv(uniform->location, 1, true, backEnd.entity->e.axis.ToFloatPtr());
				break;
			case UT_SKYBOX_MATRIX:
				qglUniformMatrix4fv(uniform->location, 1, false, backEnd.viewParms.skyBoxMatrix.ToFloatPtr());
				break;
			case UT_PORTALSKY_MATRIX:
				qglUniformMatrix4fv(uniform->location, 1, false, backEnd.viewParms.portalSkyMatrix.ToFloatPtr());
				break;
			case UT_SCREEN_MATRIX:
				qglUniformMatrix4fv(uniform->location, 1, false, backEnd.viewParms.screenMatrix.ToFloatPtr());
				break;
			case UT_XY_SCALE:
				qglUniform2fv(uniform->location, 1, backEnd.xyScale.ToFloatPtr());
				break;
			case UT_POT_ADJUST:
				qglUniform2fv(uniform->location, 1, backEnd.potAdjust.ToFloatPtr());
				break;
			case UT_LIGHT_SCALE:
				qglUniform1f(uniform->location, (r_lightScale->floatValue > 1.0f) ? r_lightScale->floatValue : 1.0f);
				break;
			default:
				Com_Error(false, "RB_SetupShaderStage: unknown shader uniform type in material '%s'", backEnd.material->name);
			}
		}
	}

	// Set up the shader parms
	for (i = 0, shaderParm = shaderStage->shaderParms; i < shaderStage->numShaderParms; i++, shaderParm++){
		parm[0] = backEnd.material->expressionRegisters[shaderParm->registers[0]];
		parm[1] = backEnd.material->expressionRegisters[shaderParm->registers[1]];
		parm[2] = backEnd.material->expressionRegisters[shaderParm->registers[2]];
		parm[3] = backEnd.material->expressionRegisters[shaderParm->registers[3]];

		switch (shaderParm->uniform->format){
		case GL_FLOAT:
			qglUniform1fv(shaderParm->uniform->location, 1, parm.ToFloatPtr());
			break;
		case GL_FLOAT_VEC2:
			qglUniform2fv(shaderParm->uniform->location, 1, parm.ToFloatPtr());
			break;
		case GL_FLOAT_VEC3:
			qglUniform3fv(shaderParm->uniform->location, 1, parm.ToFloatPtr());
			break;
		case GL_FLOAT_VEC4:
			qglUniform4fv(shaderParm->uniform->location, 1, parm.ToFloatPtr());
			break;
		}
	}

	// Set up the shader maps
	for (i = 0, shaderMap = shaderStage->shaderMaps; i < shaderStage->numShaderMaps; i++, shaderMap++){
		GL_SelectTexture(i);
		GL_BindTexture(shaderMap->texture);

		if (shaderMap->texture == tr.currentRenderTexture){
			if (backEnd.material->sort != SORT_POST_PROCESS)
				RB_CopyFrameBuffer(tr.currentRenderTexture);

			continue;
		}

		if (shaderMap->video){
			R_UpdateVideo(shaderMap->video, SEC2MS(backEnd.time));
			continue;
		}
	}
}

/*
 ==================
 RB_CleanupShaderStage
 ==================
*/
void RB_CleanupShaderStage (shaderStage_t *shaderStage){

	GL_SelectTexture(0);

	if (backEnd.overlay){
		qglDisableClientState(GL_COLOR_ARRAY);
		qglDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}
	else {
		qglDisableVertexAttribArray(11);
		qglDisableVertexAttribArray(10);

		qglDisableClientState(GL_COLOR_ARRAY);
		qglDisableClientState(GL_TEXTURE_COORD_ARRAY);
		qglDisableClientState(GL_NORMAL_ARRAY);
	}

	qglUseProgram(0);
}

/*
 ==================
 RB_SetMaterialState
 ==================
*/
void RB_SetMaterialState (void){

	if (backEnd.material->cullType == CT_FRONT_SIDED){
		GL_Enable(GL_CULL_FACE);
		GL_CullFace(GL_FRONT);
	}
	else if (backEnd.material->cullType == CT_BACK_SIDED){
		GL_Enable(GL_CULL_FACE);
		GL_CullFace(GL_BACK);
	}
	else
		GL_Disable(GL_CULL_FACE);

	if (backEnd.material->flags & MF_POLYGONOFFSET){
		GL_Enable(GL_POLYGON_OFFSET_FILL);
		GL_PolygonOffset(r_offsetFactor->floatValue, r_offsetUnits->floatValue);
	}
	else
		GL_Disable(GL_POLYGON_OFFSET_FILL);
}

/*
 ==================
 RB_SetStageState
 ==================
*/
void RB_SetStageState (stage_t *stage){

	if ((backEnd.material->flags & MF_POLYGONOFFSET) || (stage->flags & SF_POLYGONOFFSET)){
		GL_Enable(GL_POLYGON_OFFSET_FILL);
		GL_PolygonOffset(r_offsetFactor->floatValue, r_offsetUnits->floatValue);
	}
	else
		GL_Disable(GL_POLYGON_OFFSET_FILL);

	if (stage->flags & SF_BLEND){
		GL_Enable(GL_BLEND);
		GL_BlendFunc(stage->blendSrc, stage->blendDst);
	}
	else
		GL_Disable(GL_BLEND);

	if (stage->flags & SF_ALPHATEST){
		GL_Enable(GL_ALPHA_TEST);
		GL_AlphaFunc(GL_GREATER, backEnd.material->expressionRegisters[stage->alphaTestRegister]);
	}
	else
		GL_Disable(GL_ALPHA_TEST);

	if (stage->flags & SF_IGNOREALPHATEST)
		GL_DepthFunc(GL_LEQUAL);
	else
		GL_DepthFunc(GL_EQUAL);

	GL_ColorMask(!(stage->flags & SF_MASKRED), !(stage->flags & SF_MASKGREEN), !(stage->flags & SF_MASKBLUE), !(stage->flags & SF_MASKALPHA));
}

/*
 ==================
 RB_CopyFrameBuffer
 ==================
*/
void RB_CopyFrameBuffer (texture_t *texture){

	int		width, height;

	if (r_skipCopyToTextures->integerValue)
		return;

	// Find the texture dimensions
	if (glConfig.textureNonPowerOfTwoAvailable){
		width = backEnd.renderWidth;
		height = backEnd.renderHeight;
	}
	else {
		width = M_CeilPowerOfTwo(backEnd.renderWidth);
		height = M_CeilPowerOfTwo(backEnd.renderHeight);
	}

	// Update the texture
	if (width == texture->width && height == texture->height){
		tr.pc.copyToTextures++;
		tr.pc.copyToTexturePixels += backEnd.renderWidth * backEnd.renderHeight;

		GL_BindTexture(texture);

		qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, backEnd.renderWidth, backEnd.renderHeight);
	}
	else {
		tr.pc.copyToTextures++;
		tr.pc.copyToTexturePixels += width * height;

		// Check the dimensions
		if (width > glConfig.maxTextureSize || height > glConfig.maxTextureSize)
			Com_Error(false, "RB_CopyFrameBuffer: size exceeds hardware limits (%i > %i or %i > %i)", width, glConfig.maxTextureSize, height, glConfig.maxTextureSize);

		// Reallocate the texture
		texture->width = width;
		texture->height = height;

		if (texture->samples >= 3)
			texture->size = width * height * 4;
		else
			texture->size = width * height * texture->samples;

		texture->frameCount = 0;

		GL_BindTexture(texture);

		qglCopyTexImage2D(GL_TEXTURE_2D, 0, texture->format, 0, 0, width, height, 0);
	}

	// Avoid bilerp issues
	if (width != backEnd.renderWidth){
		tr.pc.copyToTexturePixels += backEnd.renderHeight;

		qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, backEnd.renderWidth, 0, backEnd.renderWidth - 1, 0, 1, backEnd.renderHeight);
	}

	if (height != backEnd.renderHeight){
		tr.pc.copyToTexturePixels += backEnd.renderWidth;

		qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, backEnd.renderHeight, 0, backEnd.renderHeight - 1, backEnd.renderWidth, 1);
	}
}


/*
 ==============================================================================

 RENDERING UTILITIES

 ==============================================================================
*/


/*
 ==================
 RB_SetupView
 ==================
*/
static void RB_SetupView (void){

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_SetupView ----------\n");

	backEnd.overlay = false;

	// Set the viewport
	qglViewport(backEnd.viewParms.x, backEnd.viewParms.y, backEnd.viewParms.width, backEnd.viewParms.height);

	// Set the scissor rectangle
	qglScissor(backEnd.viewParms.x, backEnd.viewParms.y, backEnd.viewParms.width, backEnd.viewParms.height);

	// Set the depth bounds
	if (glConfig.depthBoundsTestAvailable)
		qglDepthBoundsEXT(0.0f, 1.0f);

	// Set up projection
	GL_LoadMatrix(GL_PROJECTION, backEnd.viewParms.projectionMatrix);

	// Set up modelview
	GL_LoadMatrix(GL_MODELVIEW, backEnd.viewParms.modelviewMatrix);

	// Set the GL state
	GL_Disable(GL_CULL_FACE);
	GL_Disable(GL_POLYGON_OFFSET_FILL);
	GL_Disable(GL_BLEND);
	GL_Disable(GL_ALPHA_TEST);
	GL_Disable(GL_DEPTH_TEST);
	GL_Disable(GL_STENCIL_TEST);

	GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	GL_DepthMask(GL_TRUE);
	GL_StencilMask(255);

	GL_DepthRange(0.0f, 1.0f);

	if (backEnd.viewParms.viewType != VIEW_MIRROR)
		qglFrontFace(GL_CCW);
	else
		qglFrontFace(GL_CW);

	// Enable multisampling if available
	if (glConfig.samples)
		qglEnable(GL_MULTISAMPLE);

	// Clear the buffers
	qglClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	qglClearDepth(1.0f);
	qglClearStencil(128);

	qglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	// Check for errors
	if (!r_ignoreGLErrors->integerValue)
		GL_CheckForErrors();
}

/*
 ==================
 RB_SetupOverlay
 ==================
*/
static void RB_SetupOverlay (void){

	odMat4	orthoMatrix(2.0f, 0.0f, 0.0f, 0.0f, 0.0f, -2.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f);

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_SetupOverlay ----------\n");

	backEnd.overlay = true;

	// Set the viewport
	qglViewport(0, 0, backEnd.renderWidth, backEnd.renderHeight);

	// Set the scissor rectangle
	qglScissor(0, 0, backEnd.renderWidth, backEnd.renderHeight);

	// Set the depth bounds
	if (glConfig.depthBoundsTestAvailable)
		qglDepthBoundsEXT(0.0f, 1.0f);

	// Set up projection
	orthoMatrix[0][0] *= backEnd.xyScale[0];
	orthoMatrix[1][1] *= backEnd.xyScale[1];

	GL_LoadMatrix(GL_PROJECTION, orthoMatrix);

	// Set up modelview
	GL_LoadIdentity(GL_MODELVIEW);

	// Set the GL state
	GL_Disable(GL_CULL_FACE);
	GL_Disable(GL_POLYGON_OFFSET_FILL);
	GL_Disable(GL_BLEND);
	GL_Disable(GL_ALPHA_TEST);
	GL_Disable(GL_DEPTH_TEST);
	GL_Disable(GL_STENCIL_TEST);

	GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	GL_DepthMask(GL_FALSE);
	GL_StencilMask(0);

	GL_DepthRange(0.0f, 1.0f);

	qglFrontFace(GL_CCW);

	// Disable multisampling if available
	if (glConfig.samples)
		qglDisable(GL_MULTISAMPLE);

	// Check for errors
	if (!r_ignoreGLErrors->integerValue)
		GL_CheckForErrors();
}

/*
 ==================
 RB_SetupLightMatrix
 ==================
*/
static void RB_SetupLightMatrix (entity_t *entity, drawLight_t *light, material_t *material, stage_t *stage){

	odMat4	entityMatrix, textureMatrix, mvpMatrix;

	// Set up the light matrix
	if (entity == backEnd.scene->worldEntity)
		mvpMatrix = light->data.projectionMatrix * light->data.modelviewMatrix;
	else {
		entityMatrix.Set(entity->e.axis, entity->e.origin);

		mvpMatrix = light->data.projectionMatrix * (light->data.modelviewMatrix * entityMatrix);
	}

	if (!stage->textureStage.numTexMods)
		backEnd.localParms.lightMatrix = mvpMatrix.Transpose();
	else {
		RB_MultiplyTextureMatrix(material, &stage->textureStage, textureMatrix);

		backEnd.localParms.lightMatrix = (textureMatrix * mvpMatrix).Transpose();
	}
}

/*
 ==================
 RB_SetupFogLightMatrix
 ==================
*/
static void RB_SetupFogLightMatrix (entity_t *entity, drawLight_t *light, material_t *material, stage_t *stage){

	odVec4	distanceVector, depthVector, viewDepthVector;
	float	distanceScale, depthScale;

	// Compute the fog distance scale
	distanceScale = light->materialParms[MATERIALPARM_ALPHA];

	if (distanceScale > 1.0f)
		distanceScale = 0.5f / distanceScale;
	else
		distanceScale = 0.5f / 500.0f;

	// Compute the fog depth scale
	depthScale = light->materialParms[MATERIALPARM_MISC];

	if (depthScale > 1.0f)
		depthScale = 0.5f / depthScale;
	else
		depthScale = 0.5f / 500.0f;

	// Set up the distance vector
	distanceVector[0] = backEnd.localParms.viewMatrix[0][2] * distanceScale;
	distanceVector[1] = backEnd.localParms.viewMatrix[1][2] * distanceScale;
	distanceVector[2] = backEnd.localParms.viewMatrix[2][2] * distanceScale;
	distanceVector[3] = backEnd.localParms.viewMatrix[3][2] * distanceScale + 0.5f;

	// Set up the depth vector
	if (entity == backEnd.scene->worldEntity){
		depthVector[0] = light->data.planeNormal[0] * depthScale;
		depthVector[1] = light->data.planeNormal[1] * depthScale;
		depthVector[2] = light->data.planeNormal[2] * depthScale;
		depthVector[3] = -light->data.planeDist * depthScale + 0.5f;
	}
	else {
		depthVector[0] = entity->e.axis[0].Dot(light->data.planeNormal) * depthScale;
		depthVector[1] = entity->e.axis[1].Dot(light->data.planeNormal) * depthScale;
		depthVector[2] = entity->e.axis[2].Dot(light->data.planeNormal) * depthScale;
		depthVector[3] = (entity->e.origin.Dot(light->data.planeNormal) - light->data.planeDist) * depthScale + 0.5f;
	}

	// Set up the view-depth vector
	viewDepthVector[0] = 0.0f;
	viewDepthVector[1] = 0.0f;
	viewDepthVector[2] = 0.0f;
	viewDepthVector[3] = (backEnd.viewParms.origin.Dot(light->data.planeNormal) - light->data.planeDist) * depthScale + 0.5f;

	// Set up the object planes
	GL_SelectTexture(0);

	qglTexGenfv(GL_S, GL_OBJECT_PLANE, distanceVector.ToFloatPtr());
	qglTexGenfv(GL_T, GL_OBJECT_PLANE, distanceVector.ToFloatPtr());

	GL_SelectTexture(1);

	qglTexGenfv(GL_S, GL_OBJECT_PLANE, depthVector.ToFloatPtr());
	qglTexGenfv(GL_T, GL_OBJECT_PLANE, viewDepthVector.ToFloatPtr());
}

/*
 ==================
 RB_SetupBlendLightMatrix
 ==================
*/
static void RB_SetupBlendLightMatrix (entity_t *entity, drawLight_t *light, material_t *material, stage_t *stage){

	odMat4	entityMatrix, textureMatrix, mvpMatrix;

	// Set up the light matrix
	if (entity == backEnd.scene->worldEntity)
		mvpMatrix = light->data.projectionMatrix * light->data.modelviewMatrix;
	else {
		entityMatrix.Set(entity->e.axis, entity->e.origin);

		mvpMatrix = light->data.projectionMatrix * (light->data.modelviewMatrix * entityMatrix);
	}

	if (!stage->textureStage.numTexMods)
		backEnd.localParms.lightMatrix = mvpMatrix.Transpose();
	else {
		RB_MultiplyTextureMatrix(material, &stage->textureStage, textureMatrix);

		backEnd.localParms.lightMatrix = (textureMatrix * mvpMatrix).Transpose();
	}

	// Set up the object planes
	GL_SelectTexture(0);

	qglTexGenfv(GL_S, GL_OBJECT_PLANE, backEnd.localParms.lightMatrix[0].ToFloatPtr());
	qglTexGenfv(GL_T, GL_OBJECT_PLANE, backEnd.localParms.lightMatrix[1].ToFloatPtr());
	qglTexGenfv(GL_Q, GL_OBJECT_PLANE, backEnd.localParms.lightMatrix[3].ToFloatPtr());

	GL_SelectTexture(1);

	qglTexGenfv(GL_S, GL_OBJECT_PLANE, backEnd.localParms.lightMatrix[2].ToFloatPtr());
}

/*
 ==================
 RB_SetEntityState
 ==================
*/
void RB_SetEntityState (entity_t *entity){

	odMat4	entityMatrix;

	// Transform view origin and axis into local space and set up the view
	// matrix
	if (entity == backEnd.scene->worldEntity || entity->e.type != RE_MODEL){
		backEnd.localParms.viewOrigin = backEnd.viewParms.origin;
		backEnd.localParms.viewAxis = backEnd.viewParms.axis;
		backEnd.localParms.viewMatrix = backEnd.viewParms.modelviewMatrix;
	}
	else {
		entity->e.axis.ProjectVector(backEnd.viewParms.origin - entity->e.origin, backEnd.localParms.viewOrigin);

		backEnd.localParms.viewAxis = entity->e.axis * backEnd.viewParms.axis;

		entityMatrix.Set(entity->e.axis, entity->e.origin);
		backEnd.localParms.viewMatrix = backEnd.viewParms.modelviewMatrix * entityMatrix;
	}

	GL_LoadMatrix(GL_MODELVIEW, backEnd.localParms.viewMatrix);

	// Set the depth range
	if (entity->e.depthHack)
		GL_DepthRange(0.0f, entity->e.depthHack);
	else
		GL_DepthRange(0.0f, 1.0f);
}

/*
 ==================
 RB_SetLightState
 ==================
*/
void RB_SetLightState (entity_t *entity, drawLight_t *light, material_t *material, stage_t *stage){

	// Transform light origin into local space
	if (entity == backEnd.scene->worldEntity)
		backEnd.localParms.lightOrigin = light->data.origin;
	else
		entity->e.axis.ProjectVector(light->data.origin - entity->e.origin, backEnd.localParms.lightOrigin);

	// Set up the light matrix if needed
	if (!material || !stage)
		return;

	switch (material->lightType){
	case LT_GENERIC:
		RB_SetupLightMatrix(entity, light, material, stage);
		break;
	case LT_AMBIENT:
		RB_SetupLightMatrix(entity, light, material, stage);
		break;
	case LT_FOG:
		RB_SetupFogLightMatrix(entity, light, material, stage);
		break;
	case LT_BLEND:
		RB_SetupBlendLightMatrix(entity, light, material, stage);
		break;
	}
}

/*
 ==================
 RB_DrawElements
 ==================
*/
void RB_DrawElements (void){

	tr.pc.draws++;

	if (!backEnd.shadowVolume){
		tr.pc.indices += backEnd.numIndices;
		tr.pc.vertices += backEnd.numVertices;

		qglDrawElements(GL_TRIANGLES, backEnd.numIndices, GL_INDEX_TYPE, backEnd.indices);
	}
	else {
		tr.pc.shadowIndices += backEnd.numIndices;
		tr.pc.shadowVertices += backEnd.numVertices;

		qglDrawElements(GL_TRIANGLES, backEnd.numIndices, GL_INDEX_TYPE, backEnd.shadowIndices);
	}
}


/*
 ==============================================================================

 RENDER COMMANDS EXECUTION

 ==============================================================================
*/


/*
 ==================
 RB_RenderView
 ==================
*/
static const void *RB_RenderView (const void *data){

	const renderViewCommand_t	*cmd = (const renderViewCommand_t *)data;

	if (r_skipRender->integerValue)
		return (const void *)(cmd + 1);

	// Finish 2D rendering if needed
	if (backEnd.overlay)
		RB_EndBatch();

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_RenderView ----------\n");

	tr.pc.views++;

	// Set the state
	backEnd.time = cmd->time;
	memcpy(backEnd.materialParms, cmd->materialParms, MAX_MATERIAL_PARMS * sizeof(float));

	backEnd.scene = cmd->scene;
	backEnd.viewParms = cmd->viewParms;

	// Development tool
	if (r_skipRenderContext->integerValue)
		GLImp_ActivateContext(false);

	// Switch to 3D mode
	RB_SetupView();

	// Z-fill pass
	RB_FillDepthBuffer(cmd->numDrawMeshes, cmd->drawMeshes);

	// Stencil shadow & light interaction pass
	RB_RenderLights(cmd->numDrawLights, cmd->drawLights);

	// Ambient pass
	RB_RenderMaterialPasses(cmd->numDrawMeshes, cmd->drawMeshes, false);

	// Fog & blend light pass
	RB_RenderFogLights(cmd->numDrawFogLights, cmd->drawFogLights);

	// Ambient pass (post-process)
	RB_RenderMaterialPasses(cmd->numDrawPostProcessMeshes, cmd->drawPostProcessMeshes, true);

	// Render debug tools
	RB_RenderDebugTools(cmd);

	// Development tool
	if (r_skipRenderContext->integerValue){
		GLImp_ActivateContext(true);

		GL_SetDefaultState();
	}

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_RenderBloom
 ==================
*/
static const void *RB_RenderBloom (const void *data){

	const renderBloomCommand_t	*cmd = (const renderBloomCommand_t *)data;
	float						intensity;

	// Finish 2D rendering if needed
	if (backEnd.overlay)
		RB_EndBatch();

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_RenderBloom ----------\n");

	// Set the GL state
	GL_LoadIdentity(GL_PROJECTION);
	GL_LoadIdentity(GL_MODELVIEW);

	GL_Disable(GL_CULL_FACE);
	GL_Disable(GL_POLYGON_OFFSET_FILL);

	if (r_showBloom->integerValue)
		GL_Disable(GL_BLEND);
	else {
		GL_Enable(GL_BLEND);
		GL_BlendFunc(GL_ONE, GL_ONE);
	}

	GL_Disable(GL_ALPHA_TEST);
	GL_Disable(GL_DEPTH_TEST);
	GL_Disable(GL_STENCIL_TEST);

	GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	GL_DepthMask(GL_FALSE);
	GL_StencilMask(0);

	GL_DepthRange(0.0f, 1.0f);

	GL_EnableTexture(tr.bloomTexture->target);
	GL_BindTexture(tr.bloomTexture);

	GL_LoadIdentity(GL_TEXTURE);
	GL_TexEnv(GL_MODULATE);

	// Draw it
	if (r_showBloom->integerValue)
		intensity = 1.0f;
	else
		intensity = r_bloomIntensity->floatValue;

	while (intensity > 0.0f){
		qglColor3f(intensity, intensity, intensity);

		qglBegin(GL_QUADS);
		qglTexCoord2f(0.0f, 0.0f);
		qglVertex3f(-1.0f, -1.0f, -1.0f);
		qglTexCoord2f(1.0f, 0.0f);
		qglVertex3f( 1.0f, -1.0f, -1.0f);
		qglTexCoord2f(1.0f, 1.0f);
		qglVertex3f( 1.0f,  1.0f, -1.0f);
		qglTexCoord2f(0.0f, 1.0f);
		qglVertex3f(-1.0f,  1.0f, -1.0f);
		qglEnd();

		intensity -= 1.0f;
	}

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_CompositeBloom
 ==================
*/
static const void *RB_CompositeBloom (const void *data){

	const compositeBloomCommand_t	*cmd = (const compositeBloomCommand_t *)data;
	int								i;

	// Finish 2D rendering if needed
	if (backEnd.overlay)
		RB_EndBatch();

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_CompositeBloom ----------\n");

	// Set the GL state
	GL_LoadIdentity(GL_PROJECTION);
	GL_LoadIdentity(GL_MODELVIEW);

	GL_Disable(GL_CULL_FACE);
	GL_Disable(GL_POLYGON_OFFSET_FILL);

	if (r_bloomContrast->integerValue){
		GL_Enable(GL_BLEND);
		GL_BlendFunc(GL_ZERO, GL_DST_COLOR);
	}
	else
		GL_Disable(GL_BLEND);

	GL_Disable(GL_ALPHA_TEST);
	GL_Disable(GL_DEPTH_TEST);
	GL_Disable(GL_STENCIL_TEST);

	GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	GL_DepthMask(GL_FALSE);
	GL_StencilMask(0);

	GL_DepthRange(0.0f, 1.0f);

	// Enhance contrast
	if (r_bloomContrast->integerValue){
		// Set the GL state
		GL_DisableTexture();

		// Draw it
		qglColor3f(1.0f, 1.0f, 1.0f);

		for (i = 0; i < r_bloomContrast->integerValue; i++){
			qglBegin(GL_QUADS);
			qglVertex3f(-1.0f, -1.0f, -1.0f);
			qglVertex3f( 1.0f, -1.0f, -1.0f);
			qglVertex3f( 1.0f,  1.0f, -1.0f);
			qglVertex3f(-1.0f,  1.0f, -1.0f);
			qglEnd();
		}

		// Restore the GL state
		GL_Disable(GL_BLEND);
	}

	// Copy current render
	RB_CopyFrameBuffer(tr.bloomTexture);

	// Bind the texture
	GL_BindTexture(tr.bloomTexture);

	// Bind the program
	qglUseProgram(tr.bloomProgram->programId);

	// Blur horizontally
	qglUniform2f(backEnd.bloomParms.texCoordOffset, backEnd.xyScale[0] * r_bloomMagnitude->floatValue, 0.0f);
	qglUniform2fv(backEnd.bloomParms.xyScale, 1, backEnd.xyScale.ToFloatPtr());

	qglBegin(GL_QUADS);
	qglTexCoord2f(0.0f, 0.0f);
	qglVertex3f(-1.0f, -1.0f, -1.0f);
	qglTexCoord2f(1.0f, 0.0f);
	qglVertex3f( 1.0f, -1.0f, -1.0f);
	qglTexCoord2f(1.0f, 1.0f);
	qglVertex3f( 1.0f,  1.0f, -1.0f);
	qglTexCoord2f(0.0f, 1.0f);
	qglVertex3f(-1.0f,  1.0f, -1.0f);
	qglEnd();

	// Copy current render
	RB_CopyFrameBuffer(tr.bloomTexture);

	// Blur vertically
	qglUniform2f(backEnd.bloomParms.texCoordOffset, 0.0f, backEnd.xyScale[1] * r_bloomMagnitude->floatValue);
	qglUniform2fv(backEnd.bloomParms.xyScale, 1, backEnd.xyScale.ToFloatPtr());

	qglBegin(GL_QUADS);
	qglTexCoord2f(0.0f, 0.0f);
	qglVertex3f(-1.0f, -1.0f, -1.0f);
	qglTexCoord2f(1.0f, 0.0f);
	qglVertex3f( 1.0f, -1.0f, -1.0f);
	qglTexCoord2f(1.0f, 1.0f);
	qglVertex3f( 1.0f,  1.0f, -1.0f);
	qglTexCoord2f(0.0f, 1.0f);
	qglVertex3f(-1.0f,  1.0f, -1.0f);
	qglEnd();

	// Copy current render
	RB_CopyFrameBuffer(tr.bloomTexture);

	// Unbind the program
	qglUseProgram(0);

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_CaptureRender
 ==================
*/
static const void *RB_CaptureRender (const void *data){

	const captureRenderCommand_t	*cmd = (const captureRenderCommand_t *)data;
	texture_t						*texture;
	int								width, height;

	if (r_skipCopyToTextures->integerValue)
		return (const void *)(cmd + 1);

	texture = cmd->texture;

	// Finish 2D rendering if needed
	if (backEnd.overlay)
		RB_EndBatch();

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_CaptureRender ----------\n");

	// Find the texture dimensions
	if (glConfig.textureNonPowerOfTwoAvailable){
		width = backEnd.renderWidth;
		height = backEnd.renderHeight;
	}
	else {
		width = M_CeilPowerOfTwo(backEnd.renderWidth);
		height = M_CeilPowerOfTwo(backEnd.renderHeight);
	}

	// Update the texture
	if (width == texture->width && height == texture->height){
		tr.pc.copyToTextures++;
		tr.pc.copyToTexturePixels += backEnd.renderWidth * backEnd.renderHeight;

		GL_BindTexture(texture);

		qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, backEnd.renderWidth, backEnd.renderHeight);
	}
	else {
		tr.pc.copyToTextures++;
		tr.pc.copyToTexturePixels += width * height;

		// Check the dimensions
		if (width > glConfig.maxTextureSize || height > glConfig.maxTextureSize)
			Com_Error(false, "RB_CaptureRender: size exceeds hardware limits (%i > %i or %i > %i)", width, glConfig.maxTextureSize, height, glConfig.maxTextureSize);

		// Reallocate the texture
		texture->width = width;
		texture->height = height;

		if (texture->samples >= 3)
			texture->size = width * height * 4;
		else
			texture->size = width * height * texture->samples;

		texture->frameCount = 0;

		GL_BindTexture(texture);

		qglCopyTexImage2D(GL_TEXTURE_2D, 0, texture->format, 0, 0, width, height, 0);
	}

	// Avoid bilerp issues
	if (width != backEnd.renderWidth){
		tr.pc.copyToTexturePixels += backEnd.renderHeight;

		qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, backEnd.renderWidth, 0, backEnd.renderWidth - 1, 0, 1, backEnd.renderHeight);
	}

	if (height != backEnd.renderHeight){
		tr.pc.copyToTexturePixels += backEnd.renderWidth;

		qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, backEnd.renderHeight, 0, backEnd.renderHeight - 1, backEnd.renderWidth, 1);
	}

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_UpdateTexture
 ==================
*/
static const void *RB_UpdateTexture (const void *data){

	const updateTextureCommand_t	*cmd = (const updateTextureCommand_t *)data;
	texture_t						*texture;

	if (r_skipDynamicTextures->integerValue)
		return (const void *)(cmd + 1);

	texture = cmd->texture;

	// Finish 2D rendering if needed
	if (backEnd.overlay)
		RB_EndBatch();

	if (r_logFile->integerValue)
		QGL_LogPrintf("---------- RB_UpdateTexture ----------\n");

	tr.pc.dynamicTextures++;
	tr.pc.dynamicTexturePixels += cmd->width * cmd->height;

	// Update the texture
	if (cmd->width == texture->width && cmd->height == texture->height){
		GL_BindTexture(texture);

		qglTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cmd->width, cmd->height, GL_RGBA, GL_UNSIGNED_BYTE, cmd->image);
	}
	else {
		// Check the dimensions
		if (!M_IsPowerOfTwo(cmd->width) || !M_IsPowerOfTwo(cmd->height))
			Com_Error(false, "RB_UpdateTexture: size is not a power of two (%i x %i)", cmd->width, cmd->height);

		if (cmd->width > glConfig.maxTextureSize || cmd->height > glConfig.maxTextureSize)
			Com_Error(false, "RB_UpdateTexture: size exceeds hardware limits (%i > %i or %i > %i)", cmd->width, glConfig.maxTextureSize, cmd->height, glConfig.maxTextureSize);

		// Reallocate the texture
		texture->width = cmd->width;
		texture->height = cmd->height;

		if (texture->samples >= 3)
			texture->size = cmd->width * cmd->height * 4;
		else
			texture->size = cmd->width * cmd->height * texture->samples;

		texture->frameCount = 0;

		GL_BindTexture(texture);

		qglTexImage2D(GL_TEXTURE_2D, 0, texture->format, cmd->width, cmd->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, cmd->image);
	}

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_SetColor
 ==================
*/
static const void *RB_SetColor (const void *data){

	const setColorCommand_t	*cmd = (const setColorCommand_t *)data;

	*(dword *)backEnd.color = PackColor(cmd->color);

	backEnd.materialParms[0] = cmd->color[0];
	backEnd.materialParms[1] = cmd->color[1];
	backEnd.materialParms[2] = cmd->color[2];
	backEnd.materialParms[3] = cmd->color[3];

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_StretchPic
 ==================
*/
static const void *RB_StretchPic (const void *data){

	const stretchPicCommand_t	*cmd = (const stretchPicCommand_t *)data;
	material_t					*material;
	glIndex_t					*indices;
	glVertex_t					*vertices;
	int							i;

	material = cmd->material;

	// Switch to 2D mode if needed
	if (!backEnd.overlay){
		RB_SetupOverlay();

		// Set the state
		backEnd.time = MS2SEC(tr.frameTime);
		memset(&backEnd.materialParms[4], 0, (MAX_MATERIAL_PARMS - 4) * sizeof(float));

		// Set the render function
		backEnd.renderBatch = RB_RenderOverlayMaterial;

		// Clear the batch state
		backEnd.material = NULL;
		backEnd.entity = NULL;

		backEnd.shadowVolume = false;
		backEnd.shadowCaps = false;
	}

	// Check if the state changed
	if (material != backEnd.material || r_skipBatching->integerValue){
		// Render the last batch
		RB_EndBatch();

		// Evaluate registers if needed
		if (material != backEnd.material)
			R_EvaluateRegisters(material, backEnd.time, backEnd.materialParms, backEnd.materialParms);

		// Create a new batch
		RB_BeginBatch(material, NULL, false, false);
	}

	// Skip if condition evaluated to false
	if (!material->expressionRegisters[material->conditionRegister])
		return (const void *)(cmd + 1);

	// Check for overflow
	RB_CheckOverflow(6, 4, MAX_INDICES, MAX_VERTICES);

	// Set up indices
	indices = backEnd.indices + backEnd.numIndices;

	for (i = 0; i < 2; i++){
		indices[0] = backEnd.numVertices;
		indices[1] = backEnd.numVertices + i + 1;
		indices[2] = backEnd.numVertices + i + 2;

		indices += 3;
	}

	backEnd.numIndices += 6;

	// Set up vertices
	vertices = backEnd.vertices + backEnd.numVertices;

	vertices[0].xyz[0] = cmd->x;
	vertices[0].xyz[1] = cmd->y;
	vertices[0].xyz[2] = 0.0f;
	vertices[1].xyz[0] = cmd->x + cmd->w;
	vertices[1].xyz[1] = cmd->y;
	vertices[1].xyz[2] = 0.0f;
	vertices[2].xyz[0] = cmd->x + cmd->w;
	vertices[2].xyz[1] = cmd->y + cmd->h;
	vertices[2].xyz[2] = 0.0f;
	vertices[3].xyz[0] = cmd->x;
	vertices[3].xyz[1] = cmd->y + cmd->h;
	vertices[3].xyz[2] = 0.0f;

	vertices[0].st[0] = cmd->s1;
	vertices[0].st[1] = cmd->t1;
	vertices[1].st[0] = cmd->s2;
	vertices[1].st[1] = cmd->t1;
	vertices[2].st[0] = cmd->s2;
	vertices[2].st[1] = cmd->t2;
	vertices[3].st[0] = cmd->s1;
	vertices[3].st[1] = cmd->t2;

	for (i = 0; i < 4; i++){
		vertices->color[0] = backEnd.color[0];
		vertices->color[1] = backEnd.color[1];
		vertices->color[2] = backEnd.color[2];
		vertices->color[3] = backEnd.color[3];

		vertices++;
	}

	backEnd.numVertices += 4;

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_ShearedPic
 ==================
*/
static const void *RB_ShearedPic (const void *data){

	const shearedPicCommand_t	*cmd = (const shearedPicCommand_t *)data;
	material_t					*material;
	glIndex_t					*indices;
	glVertex_t					*vertices;
	int							i;

	material = cmd->material;

	// Switch to 2D mode if needed
	if (!backEnd.overlay){
		RB_SetupOverlay();

		// Set the state
		backEnd.time = MS2SEC(tr.frameTime);
		memset(&backEnd.materialParms[4], 0, (MAX_MATERIAL_PARMS - 4) * sizeof(float));

		// Set the render function
		backEnd.renderBatch = RB_RenderOverlayMaterial;

		// Clear the batch state
		backEnd.material = NULL;
		backEnd.entity = NULL;

		backEnd.shadowVolume = false;
		backEnd.shadowCaps = false;
	}

	// Check if the state changed
	if (material != backEnd.material || r_skipBatching->integerValue){
		// Render the last batch
		RB_EndBatch();

		// Evaluate registers if needed
		if (material != backEnd.material)
			R_EvaluateRegisters(material, backEnd.time, backEnd.materialParms, backEnd.materialParms);

		// Create a new batch
		RB_BeginBatch(material, NULL, false, false);
	}

	// Skip if condition evaluated to false
	if (!material->expressionRegisters[material->conditionRegister])
		return (const void *)(cmd + 1);

	// Check for overflow
	RB_CheckOverflow(6, 4, MAX_INDICES, MAX_VERTICES);

	// Set up indices
	indices = backEnd.indices + backEnd.numIndices;

	for (i = 0; i < 2; i++){
		indices[0] = backEnd.numVertices;
		indices[1] = backEnd.numVertices + i + 1;
		indices[2] = backEnd.numVertices + i + 2;

		indices += 3;
	}

	backEnd.numIndices += 6;

	// Set up vertices
	vertices = backEnd.vertices + backEnd.numVertices;

	vertices[0].xyz[0] = cmd->x + cmd->xShear;
	vertices[0].xyz[1] = cmd->y + cmd->yShear;
	vertices[0].xyz[2] = 0.0f;
	vertices[1].xyz[0] = cmd->x + cmd->w + cmd->xShear;
	vertices[1].xyz[1] = cmd->y - cmd->yShear;
	vertices[1].xyz[2] = 0.0f;
	vertices[2].xyz[0] = cmd->x + cmd->w - cmd->xShear;
	vertices[2].xyz[1] = cmd->y + cmd->h - cmd->yShear;
	vertices[2].xyz[2] = 0.0f;
	vertices[3].xyz[0] = cmd->x - cmd->xShear;
	vertices[3].xyz[1] = cmd->y + cmd->h + cmd->yShear;
	vertices[3].xyz[2] = 0.0f;

	vertices[0].st[0] = cmd->s1;
	vertices[0].st[1] = cmd->t1;
	vertices[1].st[0] = cmd->s2;
	vertices[1].st[1] = cmd->t1;
	vertices[2].st[0] = cmd->s2;
	vertices[2].st[1] = cmd->t2;
	vertices[3].st[0] = cmd->s1;
	vertices[3].st[1] = cmd->t2;

	for (i = 0; i < 4; i++){
		vertices->color[0] = backEnd.color[0];
		vertices->color[1] = backEnd.color[1];
		vertices->color[2] = backEnd.color[2];
		vertices->color[3] = backEnd.color[3];

		vertices++;
	}

	backEnd.numVertices += 4;

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_RenderSize
 ==================
*/
static const void *RB_RenderSize (const void *data){

	const renderSizeCommand_t	*cmd = (const renderSizeCommand_t *)data;

	// Finish 2D rendering if needed
	if (backEnd.overlay)
		RB_EndBatch();

	// Force a mode switch
	backEnd.overlay = false;

	// Set the render size
	backEnd.renderWidth = cmd->width;
	backEnd.renderHeight = cmd->height;

	// Calculate X/Y scale and power-of-two adjust for the given render size
	backEnd.xyScale[0] = 1.0f / cmd->width;
	backEnd.xyScale[1] = 1.0f / cmd->height;

	if (glConfig.textureNonPowerOfTwoAvailable || M_IsPowerOfTwo(cmd->width))
		backEnd.potAdjust[0] = 1.0f;
	else
		backEnd.potAdjust[0] = (float)cmd->width / M_CeilPowerOfTwo(cmd->width);

	if (glConfig.textureNonPowerOfTwoAvailable || M_IsPowerOfTwo(cmd->height))
		backEnd.potAdjust[1] = 1.0f;
	else
		backEnd.potAdjust[1] = (float)cmd->height / M_CeilPowerOfTwo(cmd->height);

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_SetBuffers
 ==================
*/
static const void *RB_SetBuffers (const void *data){

	const setBuffersCommand_t	*cmd = (const setBuffersCommand_t *)data;
	int							index;

	// Set the draw and read buffers
	if (r_frontBuffer->integerValue){
		qglDrawBuffer(GL_FRONT);
		qglReadBuffer(GL_FRONT);
	}
	else {
		qglDrawBuffer(GL_BACK);
		qglReadBuffer(GL_BACK);
	}

	// Clear screen if desired
	if (r_clear->integerValue){
		index = r_clearColor->integerValue & COLOR_MASK;

		GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

		qglClearColor(colorTable[index][0], colorTable[index][1], colorTable[index][2], colorTable[index][3]);
		qglClear(GL_COLOR_BUFFER_BIT);
	}

	// Check for errors
	if (!r_ignoreGLErrors->integerValue)
		GL_CheckForErrors();

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_SwapBuffers
 ==================
*/
static const void *RB_SwapBuffers (const void *data){

	const swapBuffersCommand_t	*cmd = (const swapBuffersCommand_t *)data;

	// Finish 2D rendering if needed
	if (backEnd.overlay)
		RB_EndBatch();

	// Force a mode switch
	backEnd.overlay = false;

	// Swap the buffers
	if (r_finish->integerValue || r_frontBuffer->integerValue)
		qglFinish();

	GLImp_SwapBuffers();

	// Check for errors
	if (!r_ignoreGLErrors->integerValue)
		GL_CheckForErrors();

	// Log file
	if (r_logFile->integerValue)
		QGL_LogPrintf("\n========== RB_SwapBuffers ==========\n\n");

	return (const void *)(cmd + 1);
}

/*
 ==================
 RB_ExecuteRenderCommands
 ==================
*/
void RB_ExecuteRenderCommands (const void *data){

	int		time;

	if (r_skipBackEnd->integerValue)
		return;

	if (com_speeds->integerValue)
		time = Sys_Milliseconds();

	while (1){
		switch (*(const int *)data){
		case RC_RENDER_VIEW:
			data = RB_RenderView(data);
			break;
		case RC_RENDER_BLOOM:
			data = RB_RenderBloom(data);
			break;
		case RC_COMPOSITE_BLOOM:
			data = RB_CompositeBloom(data);
			break;
		case RC_CAPTURE_RENDER:
			data = RB_CaptureRender(data);
			break;
		case RC_UPDATE_TEXTURE:
			data = RB_UpdateTexture(data);
			break;
		case RC_SET_COLOR:
			data = RB_SetColor(data);
			break;
		case RC_STRETCH_PIC:
			data = RB_StretchPic(data);
			break;
		case RC_SHEARED_PIC:
			data = RB_ShearedPic(data);
			break;
		case RC_RENDER_SIZE:
			data = RB_RenderSize(data);
			break;
		case RC_SET_BUFFERS:
			data = RB_SetBuffers(data);
			break;
		case RC_SWAP_BUFFERS:
			data = RB_SwapBuffers(data);
			break;
		case RC_END_OF_LIST:
			if (com_speeds->integerValue)
				com_timeBackEnd += (Sys_Milliseconds() - time);

			return;
		default:
			Com_Error(true, "RB_ExecuteRenderCommands: bad commandId (%i)", *(const int *)data);
		}
	}
}


/*
 ==============================================================================

 BACK-END INITIALIZATION

 ==============================================================================
*/


/*
 ==================
 RB_SetupAmbientLightProgram
 ==================
*/
static void RB_SetupAmbientLightProgram (void){

	shader_t	*vertexShader, *fragmentShader;

	// Load the vertex and fragment shaders
	vertexShader = R_FindShader("ambientLight.vs", SHADER_VERTEX);
	fragmentShader = R_FindShader("ambientLight.fs", SHADER_FRAGMENT);

	if (!vertexShader || !fragmentShader)
		Com_Error(true, "RB_SetupAmbientLightProgram: missing shaders");

	// Load the program
	tr.ambientLightProgram = R_FindProgram(vertexShader, fragmentShader);

	if (!tr.ambientLightProgram)
		Com_Error(true, "RB_SetupAmbientLightProgram: invalid program");

	// Get uniform locations
	backEnd.ambientLightParms.colorScale = R_GetProgramUniformLocation(tr.ambientLightProgram, "colorScale", GL_FLOAT, 1);
	backEnd.ambientLightParms.colorBias = R_GetProgramUniformLocation(tr.ambientLightProgram, "colorBias", GL_FLOAT, 1);
	backEnd.ambientLightParms.bumpMatrix = R_GetProgramUniformLocation(tr.ambientLightProgram, "bumpMatrix", GL_FLOAT_MAT4, 1);
	backEnd.ambientLightParms.diffuseMatrix = R_GetProgramUniformLocation(tr.ambientLightProgram, "diffuseMatrix", GL_FLOAT_MAT4, 1);
	backEnd.ambientLightParms.lightMatrix = R_GetProgramUniformLocation(tr.ambientLightProgram, "lightMatrix", GL_FLOAT_MAT4, 1);
	backEnd.ambientLightParms.diffuseColor = R_GetProgramUniformLocation(tr.ambientLightProgram, "diffuseColor", GL_FLOAT_VEC3, 1);

	// Set samplers
	R_SetProgramSamplerByName(tr.ambientLightProgram, "bumpMap", 0);
	R_SetProgramSamplerByName(tr.ambientLightProgram, "diffuseMap", 1);
	R_SetProgramSamplerByName(tr.ambientLightProgram, "lightProjectionMap", 2);
	R_SetProgramSamplerByName(tr.ambientLightProgram, "lightFalloffMap", 3);
}

/*
 ==================
 RB_SetupInteractionProgram
 ==================
*/
static void RB_SetupInteractionProgram (void){

	shader_t	*vertexShader, *fragmentShader;

	// Load the vertex and fragment shaders
	vertexShader = R_FindShader("interaction.vs", SHADER_VERTEX);
	fragmentShader = R_FindShader("interaction.fs", SHADER_FRAGMENT);

	if (!vertexShader || !fragmentShader)
		Com_Error(true, "RB_SetupInteractionProgram: missing shaders");

	// Load the program
	tr.interactionProgram = R_FindProgram(vertexShader, fragmentShader);

	if (!tr.interactionProgram)
		Com_Error(true, "RB_SetupInteractionProgram: invalid program");

	// Get uniform locations
	backEnd.interactionParms.viewOrigin = R_GetProgramUniformLocation(tr.interactionProgram, "viewOrigin", GL_FLOAT_VEC3, 1);
	backEnd.interactionParms.lightOrigin = R_GetProgramUniformLocation(tr.interactionProgram, "lightOrigin", GL_FLOAT_VEC3, 1);
	backEnd.interactionParms.colorScale = R_GetProgramUniformLocation(tr.interactionProgram, "colorScale", GL_FLOAT, 1);
	backEnd.interactionParms.colorBias = R_GetProgramUniformLocation(tr.interactionProgram, "colorBias", GL_FLOAT, 1);
	backEnd.interactionParms.bumpMatrix = R_GetProgramUniformLocation(tr.interactionProgram, "bumpMatrix", GL_FLOAT_MAT4, 1);
	backEnd.interactionParms.diffuseMatrix = R_GetProgramUniformLocation(tr.interactionProgram, "diffuseMatrix", GL_FLOAT_MAT4, 1);
	backEnd.interactionParms.specularMatrix = R_GetProgramUniformLocation(tr.interactionProgram, "specularMatrix", GL_FLOAT_MAT4, 1);
	backEnd.interactionParms.lightMatrix = R_GetProgramUniformLocation(tr.interactionProgram, "lightMatrix", GL_FLOAT_MAT4, 1);
	backEnd.interactionParms.diffuseColor = R_GetProgramUniformLocation(tr.interactionProgram, "diffuseColor", GL_FLOAT_VEC3, 1);
	backEnd.interactionParms.specularColor = R_GetProgramUniformLocation(tr.interactionProgram, "specularColor", GL_FLOAT_VEC3, 1);
	backEnd.interactionParms.specularExponent = R_GetProgramUniformLocation(tr.interactionProgram, "specularExponent", GL_FLOAT, 1);
	backEnd.interactionParms.specularBrightness = R_GetProgramUniformLocation(tr.interactionProgram, "specularBrightness", GL_FLOAT, 1);

	// Set samplers
	R_SetProgramSamplerByName(tr.interactionProgram, "bumpMap", 0);
	R_SetProgramSamplerByName(tr.interactionProgram, "diffuseMap", 1);
	R_SetProgramSamplerByName(tr.interactionProgram, "specularMap", 2);
	R_SetProgramSamplerByName(tr.interactionProgram, "lightProjectionMap", 3);
	R_SetProgramSamplerByName(tr.interactionProgram, "lightFalloffMap", 4);
}

/*
 ==================
 RB_SetupShadowProgram
 ==================
*/
static void RB_SetupShadowProgram (void){

	shader_t	*vertexShader;

	// Load the vertex shader
	vertexShader = R_FindShader("shadow.vs", SHADER_VERTEX);

	if (!vertexShader)
		Com_Error(true, "RB_SetupShadowProgram: missing shader");

	// Load the program
	tr.shadowProgram = R_FindProgram(vertexShader, NULL);

	if (!tr.shadowProgram)
		Com_Error(true, "RB_SetupShadowProgram: invalid program");

	// Get uniform location
	backEnd.shadowParms.lightOrigin = R_GetProgramUniformLocation(tr.shadowProgram, "lightOrigin", GL_FLOAT_VEC3, 1);
}

/*
 ==================
 RB_SetupBloomProgram
 ==================
*/
static void RB_SetupBloomProgram (void){

	shader_t	*vertexShader, *fragmentShader;

	// Load the vertex and fragment shaders
	vertexShader = R_FindShader("bloom.vs", SHADER_VERTEX);
	fragmentShader = R_FindShader("bloom.fs", SHADER_FRAGMENT);

	if (!vertexShader || !fragmentShader)
		Com_Error(true, "RB_SetupBloomProgram: missing shaders");

	// Load the program
	tr.bloomProgram = R_FindProgram(vertexShader, fragmentShader);

	if (!tr.bloomProgram)
		Com_Error(true, "RB_SetupBloomProgram: invalid program");

	// Get uniform locations
	backEnd.bloomParms.texCoordOffset = R_GetProgramUniformLocation(tr.bloomProgram, "texCoordOffset", GL_FLOAT_VEC2, 1);
	backEnd.bloomParms.xyScale = R_GetProgramUniformLocation(tr.bloomProgram, "xyScale", GL_FLOAT_VEC2, 1);

	// Set sampler
	R_SetProgramSamplerByName(tr.bloomProgram, "bloomRender", 0);
}

/*
 ==================
 RB_InitBackEnd
 ==================
*/
void RB_InitBackEnd (void){

	// Initialize render command list
	backEnd.commandList.data = (byte *)Mem_Alloc(MAX_RENDER_COMMANDS, TAG_RENDERER);
	backEnd.commandList.size = 0;
	backEnd.commandList.overflowed = false;

	// Allocate index and vertex arrays
	backEnd.indices = (glIndex_t *)Mem_Alloc(MAX_INDICES * sizeof(glIndex_t), TAG_RENDERER);
	backEnd.vertices = (glVertex_t *)Mem_Alloc(MAX_VERTICES * sizeof(glVertex_t), TAG_RENDERER);

	backEnd.shadowIndices = (glIndex_t *)Mem_Alloc(MAX_SHADOW_INDICES * sizeof(glIndex_t), TAG_RENDERER);
	backEnd.shadowVertices = (glShadowVertex_t *)Mem_Alloc(MAX_SHADOW_VERTICES * sizeof(glShadowVertex_t), TAG_RENDERER);

	// Set up all programs
	RB_SetupAmbientLightProgram();
	RB_SetupInteractionProgram();
	RB_SetupShadowProgram();
	RB_SetupBloomProgram();
}

/*
 ==================
 RB_ShutdownBackEnd
 ==================
*/
void RB_ShutdownBackEnd (void){

	// Disable arrays
	qglDisableVertexAttribArray(11);
	qglDisableVertexAttribArray(10);

	qglDisableClientState(GL_COLOR_ARRAY);
	qglDisableClientState(GL_TEXTURE_COORD_ARRAY);
	qglDisableClientState(GL_NORMAL_ARRAY);
	qglDisableClientState(GL_VERTEX_ARRAY);

	// Clear back-end state
	memset(&backEnd, 0, sizeof(backEndState_t));
}
